//
//  TCPConnection.m
//  MailDelivery
//
//  Created by Dante Palacios on 9/24/09.
//  Copyright 2009 Dante Palacios. All rights reserved.
//

#import <stdio.h>
#import <stdlib.h> 
#import <sys/socket.h>
#import <netinet/in.h>
#import <net/if.h>
#import <arpa/inet.h>
#import <netdb.h>
#import <unistd.h>
#import <errno.h>
#import <ifaddrs.h>

#import "TCPConnection.h"

#import "NSDataAdditions.h"
#import "NSStringAdditions.h"

#import "Constants.h"
#import "Errors.h"
#import "Funtions.h"
#import "Macros.h"

#define BUFFER_SIZE 1024


@interface TCPConnection ()

@property (readwrite, retain) NSError *error;
@property (readwrite, retain) NSString *hostName;
@property (readwrite) NSInteger port;

- (NSString *)_hostReplyForCommand:(NSString *)command;

@end

@implementation TCPConnection

@synthesize error = _error;
@synthesize securityLevel = _securityLevel;
@synthesize connectionTimeout = _connectionTimeout;
@synthesize supports8BITMIME = _supports8BITMIME;
@synthesize uses8BITMIME = _uses8BITMIME;
@synthesize provideDetailedConnectionError = _provideDetailedConnectionError;
@synthesize hostName = _hostName;
@synthesize port = _port;

#pragma mark life cycle

- (id)init 
{
	self = [super init];
	if (self != nil) 
	{
		_sslContext = NULL;
		_supports8BITMIME = NO;
		_uses8BITMIME = YES;
		_provideDetailedConnectionError = YES;
		_supportsLoginAuth = NO;
		_supportsPlainAuth = NO;
		_supportsSTARTTLS = NO;
		_securityLevel = MailDeliverySecurityLevelTLSv1;
		_ssocket = -1;
		_connectionTimeout = 60.0;
	}
	return self;
}

- (id)initWithHost:(NSString *)hostName port:(NSInteger)portNumber 
{
	TCPConnection *connection = [[[self class] alloc] init];
	[connection connectToHost:hostName port:portNumber];
	if (![connection isValid]) 
	{
		[connection release];
		connection = nil;
	}
	return connection;
}

+ (id)connectionWithHost:(NSString *)hostName port:(NSInteger)portNumber 
{
	return [[[self alloc] initWithHost:hostName port:portNumber] autorelease];
}

- (void)dealloc 
{
	[self invalidate];
	[super dealloc];
}


- (void)finalize
{
	[self invalidate];
	[super finalize];
}


- (void)invalidate 
{
	if ([self isValid]) 
	{
		MailDeliveryLog(@"%@ %@", self, NSStringFromSelector(_cmd));
		
		@try {
			NSString *quit = @"QUIT\r\n";
			MailDeliveryLog(@"C:%@", quit);
			NSString *serverReply = nil; 
			serverReply = [self _hostReplyForCommand:quit];
			MailDeliveryLog(@"S:%@", serverReply);
		}
		@catch (NSException * e) {
			NSLog(@"%@ %@ \"%@\" %@", self, NSStringFromSelector(_cmd), [e name], [e reason]);
		}
		@finally {
			if (_sslContext)
			{
				MailDeliveryLog(@"%@:Shutting down SSL...", self);
				
				SSLClose(_sslContext);
				SSLDisposeContext(_sslContext);
			}
			
			MailDeliveryLog(@"%@:Closing connection with server...", self);
			
			shutdown(_ssocket, 2);
			close(_ssocket);
			_ssocket = -1;
			
			[_error release], _error = nil;
			[_hostName release], _hostName = nil;
		}
	}
}

- (void)cancelLastRequest 
{
	MailDeliveryLog(@"%@ %@", self, NSStringFromSelector(_cmd));
	self.error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
	
	[self invalidate];
}

- (BOOL)connectToHost:(NSString *)hostName port:(NSInteger)portNumber 
{
	[self invalidate];
	
	if (self.error) return NO;
	
	MailDeliveryLog(@"%@ Attempting to connect to server \"%@:%d\"", self, hostName, portNumber);
	
	NSError *outError = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Could not connect to server", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Error while connecting to the server. Verify your internet connection and/or if you are behind a firewall check your firewall configuration.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
	
	struct hostent *hostbyname = gethostbyname(hostName.UTF8String);
	if (!hostbyname) 
	{
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Error resolving the host.", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		
		return NO;
	}
	
	unsigned long remote_h_addr = *(unsigned long *)hostbyname->h_addr_list[0];
	
	struct sockaddr_in remoteaddr;
	bzero((char *)&remoteaddr, sizeof(remoteaddr));
	remoteaddr.sin_family = AF_INET;
	remoteaddr.sin_addr.s_addr = (unsigned)remote_h_addr;
	remoteaddr.sin_port = htons(portNumber);
	
	_ssocket = socket(AF_INET, SOCK_STREAM, 0);
	if (_ssocket == -1) 
	{
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Could not allocate socket.", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		
		return NO;
	}
	
	int loop = 1;
	if (setsockopt(_ssocket, SOL_SOCKET, SO_KEEPALIVE, &loop, sizeof(loop)) < 0) 
	{
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Could not set SO_KEEPALIVE.", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		return NO;
	}
	
	// Set NONBLOCK to realize a timeout
	
	int oldFlags = fcntl(_ssocket, F_GETFL, 0);
	if (fcntl(_ssocket, F_SETFL, oldFlags | O_NONBLOCK) < 0) 
	{
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Could not set O_NONBLOCK for socket.", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		return NO;
	}
	
	int connected = connect(_ssocket, (struct sockaddr *)&remoteaddr, sizeof(remoteaddr));
	if ((connected < 0) && (errno != EINPROGRESS)) 
	{
		self.error = outError;
		return NO;
	}
	
	if (fcntl(_ssocket, F_SETFL, oldFlags) < 0) {
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Could not restore old flags for socket.", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		return NO;
	}
	
	// Test if connected but with timeout
	
	fd_set fds;
	struct timeval timeout;
	timeout.tv_usec = 0;
	timeout.tv_sec = (NSUInteger)_connectionTimeout;
	
	FD_ZERO(&fds);
	FD_SET(_ssocket, &fds);
	
	connected = select(_ssocket + 1, &fds, &fds, NULL, &timeout);
	if (connected < 0) 
	{
		self.error = _provideDetailedConnectionError ? [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Could not connect to server", NSLocalizedDescriptionKey, @"Error in select().", NSLocalizedRecoverySuggestionErrorKey, nil]] : outError;
		return NO;
	}
	else if (!connected) 
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionTimeoutError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Could not connect to server", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Timeout while connecting.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
		return NO;
	}
	
	MailDeliveryLog(@"%@:Connected to server \"%@:%d\"", self, hostName, portNumber);
	
	self.hostName = hostName;
	self.port = portNumber;
	
	return YES;
}


OSStatus TCPConnectionSSLRead(SSLConnectionRef connection, void *data, size_t *dataLength)
{
	size_t			bytesToGo = *dataLength;
	size_t 			initLen = bytesToGo;
	UInt8			*currData = (UInt8 *)data;
	int				sock = *(int *)connection;
	OSStatus		rtn = noErr;
	size_t			bytesRead;
	ssize_t			rrtn;
	
	*dataLength = 0;
	
	while (1)
	{
		bytesRead = 0;
		rrtn = read(sock, currData, bytesToGo);
		if (rrtn <= 0)
		{
			/* this is guesswork... */
			int theErr = errno;
			
			if((rrtn == 0) && (theErr == 0))
			{
				/* try fix for iSync */ 
				rtn = errSSLClosedGraceful;
				//rtn = errSSLClosedAbort;
			}
			else /* do the switch */
			{
				switch(theErr)
				{
					case ENOENT:
						/* connection closed */
						rtn = errSSLClosedGraceful; 
						break;
					case ECONNRESET:
						rtn = errSSLClosedAbort;
						break;
					case 0:		/* ??? */
						rtn = errSSLWouldBlock;
						break;
					default:
						rtn = ioErr;
						break;
				}
			}
			break;
		}
		else
		{
			bytesRead = rrtn;
		}
		bytesToGo -= bytesRead;
		currData  += bytesRead;
		
		if(bytesToGo == 0)
		{
			/* filled buffer with incoming data, done */
			break;
		}
	}
	*dataLength = initLen - bytesToGo;
	return rtn;
}


OSStatus TCPConnectionSSLWrite(SSLConnectionRef connection, const void *data, size_t *dataLength)
{
	size_t		bytesSent = 0;
	int			sock = *(int *)connection;
	ssize_t		length;
	long		dataLen = *dataLength;
	const UInt8 *dataPtr = (UInt8 *)data;
	OSStatus	ortn;
	
	*dataLength = 0;
	
    do
	{
        length = write(sock, (char*)dataPtr + bytesSent, dataLen - bytesSent);
    } while ((length > 0) && ((bytesSent += length) < dataLen));
	
	if(length <= 0)
	{
		if(errno == EAGAIN)
		{
			ortn = errSSLWouldBlock;
		}
		else
		{
			ortn = ioErr;
		}
	}
	else
	{
		ortn = noErr;
	}
	*dataLength = bytesSent;
	return ortn;
}


- (BOOL)startSSL
{
	NSString *startTLS = @"STARTTLS\r\n";
	MailDeliveryLog(@"C:%@", startTLS);
	NSString *serverReply = [self _hostReplyForCommand:startTLS];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"220 "]) 
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kSSLConnectionsFailedError userInfo:[NSDictionary dictionaryWithObject:MDLocalizedString(@"Server refused the STARTTLS message", @"") forKey:NSLocalizedDescriptionKey]];
		return NO;
	}
	
	OSStatus status = SSLNewContext(false, &_sslContext);
	
	if (status != noErr)
	{
		self.error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
		return NO;
	}
	
	SSLSetConnection(_sslContext, &_ssocket);
	SSLSetIOFuncs(_sslContext, TCPConnectionSSLRead, TCPConnectionSSLWrite);
	SSLSetProtocolVersionEnabled(_sslContext, kSSLProtocolAll, false);
	if (_securityLevel == MailDeliverySecurityLevelSSLv3)
		SSLSetProtocolVersionEnabled(_sslContext, kSSLProtocol3, true);
	else
		SSLSetProtocolVersionEnabled(_sslContext, kTLSProtocol1, true);
	status = SSLHandshake(_sslContext);
	if (status != noErr)
	{
		self.error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
		return NO;
	}
	
	MailDeliveryLog(@"%@:SSL chain OK!...", self);
	return YES;
}

- (NSInteger)write:(char *)buf length:(NSInteger)len
{
	if (_sslContext)
	{
		size_t written;
		OSStatus status = SSLWrite(_sslContext, buf, len, &written);
		
		switch (status)
		{
			case noErr:
				return written;
				break;
			case errSSLClosedGraceful: case errSSLClosedAbort:
				return 0L;
				break;
			default:
				return -1L;
				break;
		}
	}
	return write(_ssocket, buf, (size_t)len);
}

- (NSInteger)read:(char *)buf length:(NSInteger)len
{
	if (_sslContext)
	{
		size_t read;
		OSStatus status = SSLRead(_sslContext, buf, len, &read);
		
		switch (status)
		{
			case noErr:
				return read;
				break;
			case errSSLClosedGraceful: case errSSLClosedAbort:
				return 0L;
				break;
			default:
				return -1L;
				break;
		}
	}
	return read(_ssocket, buf, (size_t)len);
}

#pragma mark private methods

- (NSString *)_hostReplyForCommand:(NSString *)command 
{
	if (![self isValid]) return [NSString string];
	
	NSStringEncoding encoding = NSASCIIStringEncoding;
	if (command && command.length) 
	{
		encoding = /*self.uses8BITMIME ? */command.encoding;/* : NSASCIIStringEncoding*/;
		[self write:(char *) [command cStringUsingEncoding:encoding] length:(NSInteger) [command lengthOfBytesUsingEncoding:encoding]];
	}
	
	char buffer[BUFFER_SIZE];
	NSInteger response = [self read:buffer length:BUFFER_SIZE];	
	buffer[response] = 0;
	
	return [[[NSString alloc] initWithCString:buffer encoding:encoding] autorelease];
}

#pragma mark getters and setters

- (BOOL)isValid 
{
	return (_ssocket != -1);
}

- (NSString *)hostName 
{
    return AtomicAutoreleasedGet(_hostName);
}

- (void)setHostName:(NSString *)value
{
    AtomicRetainedSet(_hostName, value);
}

- (NSError *)error 
{
	return AtomicAutoreleasedGet(_error);
}

- (void)setError:(NSError *)value 
{
    AtomicRetainedSet(_error, value);
}

+ (NSString *)localIPAddress 
{
	NSString *address = nil;
	NSURL *URL = [NSURL URLWithString:@"http://whatsmyip.islayer.com"];
	if ((MailDeliveryValidateConnectionWithURL(URL, nil) == kCFNetDiagnosticConnectionUp)) 
	{
		address = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:nil];
		if (address.length && [[address componentsSeparatedByString:@"."] count] > 3) 
		{
			return address;
		}
	}
    
	struct ifaddrs *interfaces = NULL;
	struct ifaddrs *temp_addr = NULL;
	NSInteger success = 0;
	
	// retrieve the current interfaces - returns 0 on success
	success = getifaddrs(&interfaces);
	if (success == 0) {
		// Loop through linked list of interfaces
		temp_addr = interfaces;
		while (temp_addr != NULL) 
		{
			if (temp_addr->ifa_addr->sa_family == AF_INET) 
			{
				// Check if interface is en0 which is the wifi connection on the iPhone
				if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) 
				{
					// Get NSString from C String
					address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
				}
			}
			
			temp_addr = temp_addr->ifa_next;
		}
	}
	
	// Free memory
	freeifaddrs(interfaces);
	
	return address;
}

@end

@implementation TCPConnection(SMTPStandardAdditions)

- (BOOL)autenthicateWithHost:(NSString *)hostName userName:(NSString *)userName password:(NSString *)password scheme:(NSInteger)authenticationScheme 
{
	if (![self isValid] || self.error) return NO;
	
	// Initiate interaction with the server
	
	NSString *serverReply = [self _hostReplyForCommand:nil];
	if (!serverReply) 
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryConnectionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Could not connect to server", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Server does not respond.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
		return NO;
	}
	
	MailDeliveryLog(@"S:%@", serverReply);
	
	NSString *localhost = [[self class] localIPAddress];
	if (localhost) 
		localhost = [NSString stringWithFormat:@"[%@]", localhost];
	else
		localhost = @"localhost";
	
	
	NSString *ehlo = [NSString stringWithFormat:@"EHLO %@\r\n", localhost];
	MailDeliveryLog(@"C:%@", ehlo);
	serverReply = [self _hostReplyForCommand:ehlo];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"250"]) 
	{ 
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryEHLOMessageError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Could not connect to server", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"The server refused our EHLO message.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
		return NO;
	}
	
	NSScanner *scanner = [NSScanner scannerWithString:serverReply];
	NSString *line = nil;
	
	while (![scanner isAtEnd]) {
		BOOL foundLine = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&line];
		if (foundLine) 
		{
			NSRange range = [line rangeOfString:@"8BITMIME" options:NSCaseInsensitiveSearch];
			if (range.location != NSNotFound) _supports8BITMIME = YES;
			
			range = [line rangeOfString:@"STARTTLS" options:NSCaseInsensitiveSearch];
			if (range.location != NSNotFound) 
			{
				_supportsSTARTTLS = YES;
				_supportsPlainAuth = YES;
				_supportsLoginAuth = YES;
				MailDeliveryLog(@"%@:server supports plain and login auth", self);
			}
			
			range = [line rangeOfString:@"PLAIN" options:NSCaseInsensitiveSearch];
			if (range.location != NSNotFound) 
			{
				_supportsPlainAuth = YES;
				MailDeliveryLog(@"%@:server supports plain auth", self);
			}
			
			range = [line rangeOfString:@"LOGIN" options:NSCaseInsensitiveSearch];
			if (range.location != NSNotFound) 
			{
				_supportsLoginAuth = YES;
				MailDeliveryLog(@"%@server supports login auth", self);
			}
		}
	}	
	
	if (_supportsSTARTTLS && (_securityLevel != MailDeliverySecurityLevelNone)) 
	{
		// STARTTLS
		if (![self startSSL]) return NO;
		
		// RFC 3207 says that the EHLO command is discarded by the server after starting TLS, so we have to start again:
		NSString *ehlo = [NSString stringWithFormat:@"EHLO %@\r\n", localhost];
		
		MailDeliveryLog(@"C:%@", ehlo);
		serverReply = [self _hostReplyForCommand:ehlo];
		MailDeliveryLog(@"S:%@", serverReply);
		
		if (![serverReply hasPrefix:@"250"]) 
		{ 
			self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryEHLOMessageError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Could not connect to server", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"The server refused our EHLO message.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
			return NO;
		}
	}
	
	// Authentication 
	
	BOOL schemeIsSupported = (_supportsPlainAuth || _supportsLoginAuth);
	
	if ((authenticationScheme != kAuthenticationNone) && schemeIsSupported) 
	{
		NSStringEncoding encoding = [NSString encodingForString:password];
		if ((authenticationScheme == kAuthenticationPlain) && _supportsPlainAuth) 
		{
			NSString *loginString = [NSString stringWithFormat:@"\000%@\000%@", userName, password];
			NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:encoding] encodeBase64]];
			MailDeliveryLog(@"C:%@", authString);
			serverReply = [self _hostReplyForCommand:authString];
			MailDeliveryLog(@"S:%@", serverReply);
		}
		else if ((authenticationScheme == kAuthenticationLogin) && _supportsLoginAuth) 
		{
			NSString *authString = @"AUTH LOGIN\r\n";
			MailDeliveryLog(@"C:%@", authString);
			serverReply = [self _hostReplyForCommand:authString];
			MailDeliveryLog(@"S:%@", serverReply);
			if ([serverReply hasPrefix:@"334 VXNlcm5hbWU6"]) 
			{
				authString = [NSString stringWithFormat:@"%@\r\n", [[userName dataUsingEncoding:encoding] encodeBase64]];
				MailDeliveryLog(@"C:%@", authString);
				serverReply = [self _hostReplyForCommand:authString];
				MailDeliveryLog(@"S:%@", serverReply);
				if ([serverReply hasPrefix:@"334 UGFzc3dvcmQ6"]) 
				{
					authString = [NSString stringWithFormat:@"%@\r\n", [[password dataUsingEncoding:encoding] encodeBase64]];
					MailDeliveryLog(@"C:%@", authString);
					serverReply = [self _hostReplyForCommand:authString];
					MailDeliveryLog(@"S:%@", serverReply);
				}
			}
		}
		else 
		{
			self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryUnsupportedLoginError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Unsupported login mechanism.", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Could not find a compatible authentication mechanism.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
			return NO;
			
		}
		
		if ([serverReply hasPrefix:@"530"]) 
		{
			self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryAuthenticationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"SMTP Server require SSL connection", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Try enabling SSL.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
			
			return NO;
		}
		
		// if we do not have a positive response by the server, notify and get out
		
		if (![serverReply hasPrefix:@"235"]) 
		{
			self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryAuthenticationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"Authentication failed", @""), NSLocalizedDescriptionKey, MDLocalizedString(@"Bad username or password.", @""), NSLocalizedRecoverySuggestionErrorKey, nil]];
			
			return NO;
		}
	}		
	
	return YES;
}

- (BOOL)writeEnvelopeTo:(NSString *)recipients from:(NSString *)senderAddress 
{
	if (![self isValid] || self.error) return NO;
	
	NSString *mailFrom = (_supports8BITMIME && _uses8BITMIME) ? [NSString stringWithFormat:@"MAIL FROM:<%@> BODY=8BITMIME\r\n", senderAddress] : [NSString stringWithFormat:@"MAIL FROM:<%@>\r\n", senderAddress];
	MailDeliveryLog(@"C:%@", mailFrom);
	NSString *serverReply = [self _hostReplyForCommand:mailFrom];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"250"])  //2.1.0
	{
		NSString *recoverySuggestion = MDLocalizedString(@"Sender rejected. From address is not one of your addresses.", @"");
		if ([serverReply hasPrefix:@"530"]) 
		{
			recoverySuggestion = MDLocalizedString(@"This server require authentication. Did you forgot to provide an authentication mechanism?. Please check your settings and try again later.", @"");
		}
		
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliverySenderRefusedError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:MDLocalizedString(@"The SMTP server refused the sender address", @""), NSLocalizedDescriptionKey, recoverySuggestion, NSLocalizedRecoverySuggestionErrorKey, nil]];
		
		return NO;
	}
	
	MailDeliveryLog(@"C:%@", recipients);
	serverReply = [self _hostReplyForCommand:recipients];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"250"])  //2.1.5
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryRecipientsRefusedError userInfo:[NSDictionary dictionaryWithObject:MDLocalizedString(@"All recipient addresses rejected.", @"") forKey:NSLocalizedDescriptionKey]];
		return NO;
	}	
	
	return YES;
}

- (BOOL)writeData:(NSString *)source 
{
	if (![self isValid] || self.error) return NO;
	
	NSString *data = @"DATA\r\n";
	MailDeliveryLog(@"C:%@", data);
	NSString *serverReply = [self _hostReplyForCommand:data];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"354"]) 
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryDataError userInfo:[NSDictionary dictionaryWithObject:MDLocalizedString(@"The SMTP server refused to accept the message's data.", @"") forKey:NSLocalizedDescriptionKey]];
		return NO;
	}
	
	MailDeliveryLog(@"C:%@", source);
	//MailDeliveryLog(@"C:Sending message source...");
	serverReply = [self _hostReplyForCommand:source];
	MailDeliveryLog(@"S:%@", serverReply);
	
	if (![serverReply hasPrefix:@"250"])  // 2.2.0
	{
		self.error = [NSError errorWithDomain:MailDeliveryErrorDomain code:kMailDeliveryDataError userInfo:[NSDictionary dictionaryWithObject:MDLocalizedString(@"The SMTP server refused to accept the message's data.", @"") forKey:NSLocalizedDescriptionKey]];
		
		return NO;
	}	
	
	return YES;
}

@end
